#include <stdio.h>
#include <stdlib.h>

#include "audio.h"

#if (AUDIO_BACKEND == AUDIO_ALSA)

#include <alsa/asoundlib.h>
/*
typedef struct _audio_buffer audio_buffer;

struct _audio_buffer{
  double        time;
  audio_data_t  data;
  audio_buffer* next;
};
*/
//

typedef struct _audio_instance audio_instance;

struct _audio_instance{
  snd_pcm_t *capture_handle;
  unsigned int rate;
  unsigned int chan;
  /*unsigned int size;*/
  /*audio_buffer* curr;*/
};

#define instance (*(audio_instance*)(handle))

audio_size_t audio_get_framerate(audio_handle handle){
  return instance.rate;
}

audio_size_t audio_get_nchannels(audio_handle handle){
  return instance.chan;
}

audio_handle audio_open(const char* device) {
  int err;
  snd_pcm_hw_params_t *hw_params;
  
  audio_handle* handle = malloc(sizeof(audio_instance));
  
  fprintf(stdout, "initialize audio..\n");
  
  if ((err = snd_pcm_open(&(instance.capture_handle), device, SND_PCM_STREAM_CAPTURE, SND_PCM_ASYNC)) < 0) {
    fprintf (stderr, "cannot open audio device: %s\n", snd_strerror(err));
    return NULL;
  }
  
  if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
    fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
	     snd_strerror(err));
    return NULL;
  }
  
  if((err = snd_pcm_hw_params_any(instance.capture_handle, hw_params)) < 0) {
    fprintf(stderr, "cannot initialize hardware parameter structure(%s)\n",
	    snd_strerror(err));
    return NULL;
  }
  
  if((err = snd_pcm_hw_params_set_access(instance.capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
    fprintf(stderr, "cannot set access type(%s)\n",
	    snd_strerror(err));
    return NULL;
  }

  if((err = snd_pcm_hw_params_set_format(instance.capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
    fprintf(stderr, "cannot set sample format(%s)\n",
	    snd_strerror(err));
    return NULL;
  }

  if((err = snd_pcm_hw_params_set_rate_near(instance.capture_handle, hw_params, &(instance.rate), 0)) < 0) {
    fprintf(stderr, "cannot set sample rate(%s)\n",
	    snd_strerror(err));
    return NULL;
  }
  
  if((err = snd_pcm_hw_params_get_channels_min(hw_params, &(instance.chan))) < 0) {
    fprintf(stderr, "cannot get min channel count(%s)\n",
	    snd_strerror(err));
    return NULL;
  }
  
  if((err = snd_pcm_hw_params_set_channels(instance.capture_handle, hw_params, instance.chan)) < 0) {
    fprintf(stderr, "cannot set channel count(%s)\n",
	    snd_strerror(err));
    return NULL;
  }
  
  if((err = snd_pcm_hw_params(instance.capture_handle, hw_params)) < 0) {
    fprintf(stderr, "cannot set parameters(%s)\n",
	    snd_strerror(err));
    return NULL;
  }
  
  snd_pcm_hw_params_free(hw_params);
  
  if ((err = snd_pcm_prepare(instance.capture_handle)) < 0) {
    fprintf(stderr, "cannot prepare audio interface for use (%s)\n",
	    snd_strerror(err));
    return NULL;
  }
  
  return handle;
}

#ifndef _
#define _(msg) msg
#endif

#ifndef timersub
#define	timersub(a, b, result) \
do { \
	(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
	(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
	if ((result)->tv_usec < 0) { \
		--(result)->tv_sec; \
		(result)->tv_usec += 1000000; \
	} \
} while (0)
#endif

/* I/O error handler */
static void xrun(audio_handle handle){
  snd_pcm_status_t *status;
  int res;
  int verbose = 0;
  
  snd_pcm_status_alloca(&status);
  if ((res = snd_pcm_status(instance.capture_handle, status))<0) {
    error(_("status error: %s"), snd_strerror(res));
    exit(EXIT_FAILURE);
  }
  if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
    struct timeval now, diff, tstamp;
    gettimeofday(&now, 0);
    snd_pcm_status_get_trigger_tstamp(status, &tstamp);
    timersub(&now, &tstamp, &diff);
    fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"),
	    _("overrun"),
	    diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
    if (verbose) {
      fprintf(stderr, _("Status:\n"));
      //snd_pcm_status_dump(status, log);
    }
    if ((res = snd_pcm_prepare(instance.capture_handle))<0) {
      error(_("xrun: prepare error: %s"), snd_strerror(res));
      exit(EXIT_FAILURE);
    }
    return;		/* ok, data should be accepted again */
  } if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING) {
    if (verbose) {
      fprintf(stderr, _("Status(DRAINING):\n"));
      //snd_pcm_status_dump(status, log);
    }
    
    fprintf(stderr, _("capture stream format change? attempting recover...\n"));
    if ((res = snd_pcm_prepare(instance.capture_handle))<0) {
      error(_("xrun(DRAINING): prepare error: %s"), snd_strerror(res));
      exit(EXIT_FAILURE);
    }
    return;
  }
  if (verbose) {
    fprintf(stderr, _("Status(R/W):\n"));
    //snd_pcm_status_dump(status, log);
  }
  error(_("read/write error, state = %s"), snd_pcm_state_name(snd_pcm_status_get_state(status)));
  exit(EXIT_FAILURE);
}

/* I/O suspend handler */
static void suspend(audio_handle handle){
  int res;
  
  fprintf(stderr, _("Suspended. Trying resume. ")); fflush(stderr);
  
  while ((res = snd_pcm_resume(instance.capture_handle)) == -EAGAIN)
    sleep(1);	/* wait until suspend flag is released */
  if (res < 0) {
    fprintf(stderr, _("Failed. Restarting stream. ")); fflush(stderr);
    if ((res = snd_pcm_prepare(instance.capture_handle)) < 0) {
      error(_("suspend: prepare error: %s"), snd_strerror(res));
      exit(EXIT_FAILURE);
    }
  }
  fprintf(stderr, _("Done.\n"));
}

audio_size_t audio_read_data(audio_handle handle, audio_data_t ret_data, audio_size_t length) {
  int err;
  
  //snd_pcm_start(instance.capture_handle);
  
  if((err = snd_pcm_readi(instance.capture_handle, ret_data, length)) < 0){
    if(err == -EPIPE) xrun(handle);
    else if (err == -ESTRPIPE) suspend(handle);
    else fprintf (stderr, "read from audio interface failed (%s)\n", snd_strerror(err));
  }
  
  //snd_pcm_drop(instance.capture_handle);
  
  return err;
}

void audio_close(audio_handle handle) {
  fprintf(stdout, "finalize audio..\n");
  
  snd_pcm_close(instance.capture_handle);
}

#endif

#if (AUDIO_BACKEND == AUDIO_OSS)

#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <linux/soundcard.h>

int audio_fd;

int audio_initialize(const char* device) {
  int audiomod;
  
  audio_fd = open(device, O_RDONLY);
  if (!audio_fd) {
    printf("Couldn't open audio source..\n");
    exit(1);
  }
  audiomod = 0x00ff0009; // 7fff = automatic buff, 0009 = 2^9 = 512 bytes..
  ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &audiomod);
  audiomod = AFMT_S16_LE;
  ioctl(audio_fd, SOUND_PCM_SETFMT, &audiomod);
  audiomod = 16; // 16 bits
  ioctl(audio_fd, SOUND_PCM_READ_BITS, &audiomod);
  audiomod = 1; // 1 channel
  ioctl(audio_fd, SOUND_PCM_READ_CHANNELS, &audiomod);
  audiomod = 44100; // Hz..
  ioctl(audio_fd, SOUND_PCM_READ_CHANNELS, &audiomod);
  return 0;
}

int audio_finalize() {
  close(audio_fd);
  return 0;
}

int audio_read_data(AudioData ret_data, AudioLength length) {
  ssize_t numread = read(audio_fd, ret_data, length * sizeof(int16_t));
  if (numread != length * sizeof(int16_t)) {
    perror("Error reading sound data");
    return -1;
  }
  return 0;
}

#endif
